/* 文字显示：NPM包 troika-three-text 的封装
 *
 * 用法
 *   const text = new Text(width, height, options)
 *   scene.add(text)
 */
// 第三方库
import * as THREE from "three";
// @ts-ignore
import { Text as TroikaThreeText } from "troika-three-text";
// 项目公用
import { cachedTextureLoader } from "./CachedTextureLoader";
// 资产文件
import urlNotoSansScFont from "@/assets/text/NotoSansSC-Regular.otf";

export type TextOptions = {
  width?: number;
  height?: number;
  position?: [number, number, number];
  fontSize?: number;
  fontColor?: string | number;
  fontWeight?: string;
  opacity?: number;
  outlineWidth?: number;
  outlineColor?: string | number;
  lineHeight?: number;
  letterSpacing?: number;
  textIndent?: number;
  showHelper?: boolean;
  fontUrl?: string;
  clip?: boolean;
  textAlign?: TextAlign;
  verticalAlign?: VerticalAlign;
  paddingX?: number;
  paddingY?: number;
  backgroundUrl?: string;
};
export type TextAlign = "left" | "center" | "right";
export type VerticalAlign = "top" | "center" | "bottom";

export default class Text extends THREE.Object3D {
  // 静态
  static DEFAULT_FONT_SIZE = 0.2;
  static DEFAULT_FONT_URL = urlNotoSansScFont;
  static DEFAULT_FONT_COLOR = 0xffffff;
  static DEFAULT_FONT_WEIGHT = "normal";
  static DEFAULT_TEXT_ALIGN: TextAlign = "center";
  static DEFAULT_VERTICAL_ALIGN: VerticalAlign = "center";
  static DEFAULT_AREA_SIZE: [number, number] = [1, 0.5];
  static DEFAULT_PADDING: [number, number] = [0.05, 0.05];
  // 主属性
  text0: any; // 被封装的原文字对象
  helper = new THREE.Group(); // 辅助器
  // 主设置
  text: string; // 文字内容
  width = Text.DEFAULT_AREA_SIZE[0]; // 文字区域宽度
  height = Text.DEFAULT_AREA_SIZE[1]; // 文字区域高度
  fontSize = Text.DEFAULT_FONT_SIZE; // 字号
  fontColor: string | number = Text.DEFAULT_FONT_COLOR; // 字色
  fontWeight = Text.DEFAULT_FONT_WEIGHT; // 字重
  fontUrl = Text.DEFAULT_FONT_URL; // 字体文件路径，只支持字体格式 TTF, OTF, WOFF
  opacity = 1; // 不透明度
  // 描边
  outlineWidth = 0; // 描边粗细，默认0，不描边
  outlineColor: string | number = 0x000000; // 描边色，默认黑色
  // 布局设置
  lineHeight = 1.4; // 行高，单位为几倍文字大小，默认取前端国际标准:1.4
  letterSpacing = 0.04; // 字符间距，单位为几倍文字大小
  textIndent = 0; // 段落缩进
  clip = true; // 裁剪超出矩形区域的
  paddingX = Text.DEFAULT_PADDING[0]; // 水平内边距
  paddingY = Text.DEFAULT_PADDING[1]; // 垂直内边距
  backgroundUrl: string = ""; // 背景图片地址
  textAlign: TextAlign = Text.DEFAULT_TEXT_ALIGN; // 水平对齐
  verticalAlign: VerticalAlign = Text.DEFAULT_VERTICAL_ALIGN; // 垂直对齐
  constructor(text: string, options: TextOptions = {}) {
    super();

    // 主属性
    this.text = text;
    if (options.width != null) this.width = options.width;
    if (options.height != null) this.height = options.height;
    if (options.position) this.position.set(...options.position);
    if (options.fontSize != null) this.fontSize = options.fontSize;
    if (options.fontColor != null) this.fontColor = options.fontColor;
    if (options.fontWeight != null) this.fontWeight = options.fontWeight;
    if (options.fontUrl) {
      const url = options.fontUrl.trim().toLowerCase();
      const isTtf = url.endsWith(".ttf");
      const isOtf = url.endsWith(".otf");
      const isWoff = url.endsWith(".woff");
      if (isTtf || isOtf || isWoff) this.fontUrl = options.fontUrl;
      else console.warn("[Text] 字体文件只支持 TTF, OTF, WOFF 格式");
    }
    if (options.opacity != null) this.opacity = options.opacity;
    // 描边
    if (options.outlineWidth != null) this.outlineWidth = options.outlineWidth;
    if (options.outlineColor != null) this.outlineColor = options.outlineColor;
    // 布局
    if (options.lineHeight != null) this.lineHeight = options.lineHeight;
    if (options.letterSpacing != null)
      this.letterSpacing = options.letterSpacing;
    if (options.textIndent != null) this.textIndent = options.textIndent;
    if (options.clip != null) this.clip = options.clip;
    if (options.textAlign) this.textAlign = options.textAlign;
    if (options.verticalAlign) this.verticalAlign = options.verticalAlign;
    if (options.paddingX) this.paddingX = options.paddingX;
    if (options.paddingY) this.paddingY = options.paddingY;
    if (options.backgroundUrl) this.backgroundUrl = options.backgroundUrl;

    // 创建对象
    this.createText();
    this.createBackgroundImage();
    this.add(this.helper);
    if (!options.showHelper) this.helper.visible = false;
    this.createHelper();
    this.updateText();
  }

  // 创建文字
  createText() {
    const text = new TroikaThreeText();
    text.text = this.text;
    text.color = this.fontColor;
    text.fontWeight = this.fontWeight;
    text.material.opacity = this.opacity;
    text.depthOffset = -1;
    text.fontSize = this.fontSize;
    if (this.fontUrl) text.font = this.fontUrl;
    // text.textAlign = 'left'
    // 描边
    text.outlineWidth = this.outlineWidth;
    text.outlineColor = this.outlineColor;
    // 布局
    text.lineHeight = this.lineHeight;
    text.letterSpacing = this.letterSpacing;
    text.textIndent =
      this.textIndent * this.fontSize * (1 + this.letterSpacing);
    text.overflowWrap = "break-word"; // 英文按说不该这样，但不这样的话，中文段落就不会换行
    this.text0 = text;
    this.#applyLayout();
    this.add(text);
  }

  // 创建背景图
  createBackgroundImage() {
    // 测试：创建背景图片
    const geometry = new THREE.PlaneGeometry();
    const material = new THREE.MeshStandardMaterial({
      map: cachedTextureLoader.loadSrgbSync(this.backgroundUrl, true),
    });
    const backgroundImage = new THREE.Mesh(geometry, material);
    backgroundImage.name = "BackgroundImage";
    // 大小(位置不需要、在父节点上已经应用过了）
    backgroundImage.scale.set(this.width, this.height, 1);
    backgroundImage.visible = this.backgroundUrl != "";
    this.add(backgroundImage);
  }

  // 设置位置
  setPosition(x: number, y: number, z: number) {
    this.position.set(x, y, z);
  }

  // 设置水平对齐
  setTextAlign(textAlign: TextAlign) {
    this.textAlign = textAlign;
    this.#applyLayout();
  }

  // 设置垂直对齐
  setVerticalAlign(verticalAlign: VerticalAlign) {
    this.verticalAlign = verticalAlign;
    this.#applyLayout();
  }

  // 设置大小（文字、背景图）
  setSize(width: number, height: number) {
    this.width = width;
    this.height = height;
    this.#applyLayout();
  }

  // 设置内边距
  setPadding(paddingX: number, paddingY: number) {
    this.paddingX = paddingX;
    this.paddingY = paddingY;
    this.#applyLayout();
  }

  // 设置背景图
  setBackgroundUrl(backgroundUrl: string | undefined) {
    if (backgroundUrl == undefined) backgroundUrl = "";
    this.backgroundUrl = backgroundUrl;

    const backgroundMesh = this.children.find(
      (node) => node.name == "BackgroundImage"
    );
    if (backgroundMesh) {
      const material = (backgroundMesh as THREE.Mesh)
        .material as THREE.MeshStandardMaterial;
      if (!this.backgroundUrl) {
        material.map = null;
        backgroundMesh.visible = false;
      } else {
        const texture = cachedTextureLoader.loadSrgbSync(
          this.backgroundUrl,
          true
        )!;
        material.map = texture;
        backgroundMesh.visible = true;
      }
    } else {
      console.warn("[Text.setBackgroundUrl] 找不到设置背景图的目标 Mesh");
    }
    this.#applyLayout();
  }

  // 应用布局（区域宽高、内边距、对齐方式、背景图大小）
  #applyLayout() {
    const text = this.text0;
    if (text == null) return;

    // 水平对齐
    let [clipMinX, clipMinY] = [0, 0];
    if (this.textAlign == "left") {
      text.anchorX = "left";
      text.position.x = -this.width / 2 + this.paddingX;
      clipMinX = 0;
    } else if (this.textAlign == "center") {
      text.anchorX = "center";
      text.position.x = 0;
      clipMinX = -this.width / 2 + this.paddingX;
      text.textAlign = "center";
    } else {
      text.anchorX = "right";
      text.position.x = this.width / 2 - this.paddingX;
      clipMinX = -this.width + this.paddingX * 2;
      text.textAlign = "right";
    }

    // 垂直对齐
    if (this.verticalAlign == "top") {
      text.anchorY = "top";
      text.position.y = this.height / 2 - this.paddingY;
      clipMinY = -this.height + this.paddingY * 2;
    } else if (this.verticalAlign == "center") {
      text.anchorY = "middle";
      text.position.y = 0;
      clipMinY = -this.height / 2 + this.paddingY;
    } else {
      text.anchorY = "bottom";
      text.position.y = -this.height / 2 + this.paddingY;
      clipMinY = 0;
    }

    // 裁剪矩形
    const clipMaxX = clipMinX + this.width - this.paddingX * 2;
    const clipMaxY = clipMinY + this.height - this.paddingY * 2;
    if (this.clip) text.clipRect = [clipMinX, clipMinY, clipMaxX, clipMaxY];

    // 宽高
    text.maxWidth = this.width - this.paddingX * 2;
    this.rectOuter?.scale.set(this.width, this.height, 1);
    this.rectInner?.scale.set(
      this.width - this.paddingX * 2,
      this.height - this.paddingY * 2,
      1
    );

    // 背景图大小
    const backgroundMesh = this.children.find(
      (node) => node.name == "BackgroundImage"
    );
    if (backgroundMesh) {
      backgroundMesh.scale.set(this.width, this.height, 1);
    }
  }

  // 创建辅助器（外轮廓与内轮廓）
  rectOuter!: THREE.LineSegments;
  rectInner!: THREE.LineSegments;
  createHelper() {
    const rectOuter = new THREE.LineSegments(
      new THREE.EdgesGeometry(new THREE.PlaneGeometry()),
      new THREE.LineBasicMaterial({
        color: 0x00ffff,
        transparent: true,
        opacity: 0.5,
        depthTest: false,
      })
    );
    rectOuter.scale.set(this.width, this.height, 1);
    this.rectOuter = rectOuter;
    this.helper.add(rectOuter);

    const rectInner = new THREE.LineSegments(
      new THREE.EdgesGeometry(new THREE.PlaneGeometry()),
      new THREE.LineBasicMaterial({
        color: 0xffff00,
        transparent: true,
        opacity: 0.5,
        depthTest: false,
      })
    );
    rectInner.scale.set(
      this.width - this.paddingX * 2,
      this.height - this.paddingY * 2,
      1
    );
    this.rectInner = rectInner;
    this.helper.add(rectInner);
  }

  setContent(text: string) {
    this.text0.text = text;
    this.updateText();
  }

  setFontColor(color: number) {
    this.text0.color = color;
    this.updateText();
  }

  setFontSize(size: number) {
    this.text0.fontSize = size;
    this.updateText();
  }

  setFontWeight(weight: string) {
    this.text0.fontWeight = weight;
    this.updateText();
  }

  updateText() {
    this.text0.sync();
  }

  showHelper() {
    this.helper.visible = true;
  }

  hideHelper() {
    this.helper.visible = false;
  }
}
